<template>
  <div class="hello">
    <el-container>
      <el-aside width="200px" class="aside">
        <el-row type="flex" :gutter="2" justify="center">
          <el-col>
            高：
            <el-input-number v-model="row" controls-position="right" :min="5" :max="25"></el-input-number>
          </el-col>
        </el-row>
        <el-row type="flex" :gutter="2" justify="center">
          <el-col>
            宽：
            <el-input-number v-model="col" controls-position="right" :min="5" :max="50"></el-input-number>
          </el-col>
        </el-row>
        <el-row type="flex" :gutter="2" justify="center">
          <el-button type="success" round data-type="1" @click="setStator">设置起点</el-button>
        </el-row>
        <el-row type="flex" :gutter="2" justify="center">
          <el-button type="warning" round data-type="2" @click="setStator">设置路障</el-button>
        </el-row>
        <el-row type="flex" :gutter="2" justify="center">
          <el-button type="danger" round data-type="3" @click="setStator">设置终点</el-button>
        </el-row>
        <el-row type="flex" :gutter="2" justify="center">
          <el-button type="primary" round @click="find">计算</el-button>
        </el-row>
      </el-aside>
      <el-main>
        <el-row :gutter="1" type="flex" justify="center" v-for="ri in row" :key="ri">
          <div
            class="grid-content bg-purple"
            :class="setStartPointBG(ri,ci)"
            :data-row="ri"
            :data-col="ci"
            v-for="ci in col"
            :key="ci"
            :data-id="(ri-1)*col+ci"
            :ref="((ri-1)*col+ci)"
            @click="action"
            @mouseover="mouseOver"
          ></div>
        </el-row>
      </el-main>
    </el-container>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      row: 20,
      col: 20,
      actionBtn: 0,
      start: {
        y: 1,
        x: 1
      },
      end: {
        y: 20,
        x: 20
      },
      draw: false,
      opens: [],
      closes: [],
      words: []
    };
  },
  methods: {
    setStator(e) {
      this.actionBtn = e.currentTarget.dataset.type;
    },
    action(e) {
      let rowIndex = e.currentTarget.dataset.row,
        colIndex = e.currentTarget.dataset.col;
      switch (this.actionBtn) {
        case "1":
          this.start = {
            y: rowIndex,
            x: colIndex
          };
          break;
        case "2":
          this.draw = !this.draw;
          this.mouseOver(e);
          break;
        case "3":
          this.end = {
            y: rowIndex,
            x: colIndex
          };
          break;
        default:
          break;
      }
    },
    setStartPointBG(y, x) {
      if ((this.start.y == y) & (this.start.x == x)) {
        return "bg-start";
      } else if ((this.end.y == y) & (this.end.x == x)) {
        return "bg-end";
      } else {
        return "bg-purple";
      }
    },
    mouseOver(e) {
      if (this.actionBtn == 2 && this.draw) {
        let data = e.currentTarget.dataset,
          x = Number(data.col),
          y = Number(data.row);
        if (this.words[y]?.get(x) === 1) {
          this.words[y].set(x, 0);
          this.$refs[data.id][0].style.backgroundColor = "#d3dce1";
        } else {
          let map = this.words[y] || new Map();
          map.set(x, 1);
          this.words[y] = map;
          this.$refs[data.id][0].style.backgroundColor = "#E6A23C";
        }
      }
    },
    find() {
      let that = this,
        start = that.start,
        end = that.end,
        opens = [],
        closes = [],
        startNode = new Node(start.x, start.y),
        endNode = new Node(end.x, end.y),
        result = null;
      opens.push(startNode);
      while (opens.length > 0) {
        let { node, index } = this.findMindPath(opens);
        opens.splice(index, 1);
        closes.push(node);
        for (const neighbour of this.findNeighbors(node, opens, closes)) {
          neighbour.insertParentNode(node, endNode);
          opens.push(neighbour);
        }
        if (node.x == end.x && node.y == end.y) {
          result = node;
          break;
        }
      }
      while (result?.parent?.parent != null) {
        this.$refs[result.parent.getNodeId(this.col)][0].style.backgroundColor =
          "#ffd400";
        result = result.parent;
      }
    },
    findMindPath(opens) {
      let node = opens[0];
      let index = 0;
      opens.forEach(function(temp, i) {
        if (node.f > temp.f) {
          node = temp;
          index = i;
        }
      });
      return { node, index };
    },
    findNeighbors(node, opens, closes) {
      let neighbors = [];
      if (this.hasValidNode(node.x, node.y - 1, opens, closes)) {
        neighbors.push(new Node(node.x, node.y - 1));
      }
      if (this.hasValidNode(node.x, node.y + 1, opens, closes)) {
        neighbors.push(new Node(node.x, node.y + 1));
      }
      if (this.hasValidNode(node.x - 1, node.y, opens, closes)) {
        neighbors.push(new Node(node.x - 1, node.y));
      }
      if (this.hasValidNode(node.x + 1, node.y, opens, closes)) {
        neighbors.push(new Node(node.x + 1, node.y));
      }
      return neighbors;
    },
    hasValidNode(x, y, opens, closes) {
      if (
        x < 1 ||
        x > this.col ||
        y < 1 ||
        y > this.row ||
        this.words[y]?.get(x) == 1
      ) {
        return false;
      }

      if (opens.filter(n => n.x == x && n.y == y).length > 0) {
        return false;
      }

      if (closes.filter(n => n.x == x && n.y == y).length > 0) {
        return false;
      }

      return true;
    }
  }
};

class Node {
  x;
  y;
  f = 0;
  g = 0;
  h = 0;
  id = 0;
  parent = null;
  constructor(x, y) {
    this.x = Number(x);
    this.y = Number(y);
  }
  insertParentNode(parent, end) {
    this.parent = parent;
    this.g = parent.g + 1;
    this.h = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);
    this.f = this.g + this.h;
  }
  getNodeId(col) {
    return (this.y - 1) * col + this.x;
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style >
.aside > div {
  margin-top: 10px;
}
.bg-purple {
  background: #d3dce1;
}
.bg-start {
  background: #67c23a;
}
.bg-end {
  background: #f56c6c;
}
.grid-content {
  width: 20px;
  height: 20px;
  margin: 1px;
}
</style>
